#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>

#include <psp2/io/fcntl.h>
#include <psp2/io/stat.h>

#include "config.h"
#include "utils.h"

const char taihen_config_recovery_header[] =
    "# This file is used as an alternative if ux0:tai/config.txt is not found.\n";

const char taihen_config_header[] =
    "# For users plugins, you must refresh taiHEN from HENkaku Settings for\n"
    "# changes to take place.\n"
    "# For kernel plugins, you must reboot for changes to take place.\n";

const char taihen_config[] =
    "*KERNEL\n"
    "# henkaku.skprx is hard-coded to load and is not listed here\n"
    "*main\n"
    "# main is a special titleid for SceShell\n"
    "ur0:tai/henkaku.suprx\n"
    "*NPXS10015\n"
    "# this is for modifying the version string\n"
    "ur0:tai/henkaku.suprx\n"
    "*NPXS10016\n"
    "# this is for modifying the version string in settings widget\n"
    "ur0:tai/henkaku.suprx\n";

const char *taihen_config_paths[] = {
    "ux0:tai/config.txt",
    "ur0:tai/config.txt",
    "uma0:tai/config.txt",
    "imc0:tai/config.txt",
    "xmc0:tai/config.txt",
};

#define N_TAIHEN_CONFIG_PATHS (sizeof(taihen_config_paths) / sizeof(char **))

int resetTaihenConfig(const char *path)
{
    int fd;

    char base_dir[MAX_PATH_LENGTH];
    makeBaseDirectory(base_dir, path, MAX_PATH_LENGTH);
    sceIoMkdir(base_dir, 0777);

    // write default config
    sceIoRemove(path);
    fd = sceIoOpen(path, SCE_O_TRUNC | SCE_O_CREAT | SCE_O_WRONLY, 6);
    if (strncmp(path, "ur0:", 4) == 0)
    {
        sceIoWrite(fd, taihen_config_recovery_header, sizeof(taihen_config_recovery_header) - 1);
    }
    sceIoWrite(fd, taihen_config_header, sizeof(taihen_config_header) - 1);
    sceIoWrite(fd, taihen_config, sizeof(taihen_config) - 1);
    sceIoClose(fd);

    return 0;
}

int resetTaihenConfigEx()
{
    int i;
    for (i = 0; i < N_TAIHEN_CONFIG_PATHS; i++)
    {
        resetTaihenConfig(taihen_config_paths[i]);
    }

    return 0;
}

int getLineLength(const char *buf, int size)
{
    int i = 0;
    uint8_t *p = (uint8_t *)buf;

    for (i = 0; i < size; i++)
    {
        if (p[i] < 0x20 && p[i] != '\t')
        {
            i++;
            break;
        }
    }

    return i;
}

int modifyTaiConfigBuffer(char *buffer, int *size, int max_size, char *location, char *plugin_path, int priority)
{
    int location_len = strnlen(location, MAX_NAME_LENGTH);
    int plugin_path_len = strnlen(plugin_path, MAX_PATH_LENGTH);
    int remaining_size = *size;
    char *p = buffer;

    int location_geted = 0;
    char *location_add_pointer = buffer;
    char *path_add_pointer = buffer;
    int location_add_pointer_decided = 0;
    int path_add_pointer_decided = 0;
    int is_kernel_location = 0;

    if (strcmp(location, "*KERNEL") == 0)
        is_kernel_location = 1;

    // Skip UTF-8 bom
    uint32_t bom = 0xBFBBEF;
    if (strncmp(p, (char *)&bom, 3) == 0)
    {
        p += 3;
        remaining_size -= 3;
    }

    char name[MAX_NAME_LENGTH];
    makeFileNameEx(name, MAX_NAME_LENGTH, plugin_path, plugin_path_len);
    int name_len = strnlen(name, MAX_NAME_LENGTH);
    char tmp_name[MAX_NAME_LENGTH];
    int tmp_name_len = 0;

    char *line;
    int ori_len, len;

    while (remaining_size > 0)
    {
        ori_len = getLineLength(p, remaining_size);
        if (ori_len <= 0)
            break;

        remaining_size -= ori_len;
        line = p;
        len = ori_len;

        // Trim string
        while (*((unsigned char *)line) <= 0x20)
        {
            line++;
            len--;
        }
        while (len > 0 && ((unsigned char *)line)[len - 1] <= 0x20)
        {
            len--;
        }

        if (*((unsigned char *)line) == '#' || len < 1)
        {
            p += ori_len;
            if (location_geted && !path_add_pointer_decided)
                path_add_pointer = p;
            continue;
        }

        int is_location_line = 0;

        if (*((unsigned char *)line) == '*')
        {
            is_location_line = 1;
            if (is_kernel_location && !location_add_pointer_decided)
            {
                location_add_pointer_decided = 1;
                location_add_pointer = p;
            }
        }

        if (location_geted && !path_add_pointer_decided)
        {
            path_add_pointer = p;
            if (priority || is_location_line)
                path_add_pointer_decided = 1;
        }

        if (!is_location_line)
        {
            makeFileNameEx(tmp_name, MAX_NAME_LENGTH, line, len);
            tmp_name_len = strnlen(tmp_name, MAX_NAME_LENGTH);
            if (tmp_name_len == name_len && strncasecmp(tmp_name, name, name_len) == 0)
            { // if the line's file name is the same as the plugin name, remove it
                char *src = p + ori_len;
                char *dst = p;
                int move_len = buffer + *size - src;
                int new_size = *size + (dst - src);
                if (new_size > max_size - 1)
                    return -1;
                memmove(dst, src, move_len); // move <<
                *size = new_size;
                continue;
            }
        }
        else
        {
            if (!location_geted && (len == location_len && strncmp(line, location, location_len) == 0))
            {
                location_geted = 1;
                path_add_pointer = p + ori_len;
            }
        }

        p += ori_len;
    }

    if (!location_geted)
    { // Add location
        if (!is_kernel_location)
            location_add_pointer = buffer + *size;

        char *src = location_add_pointer;
        char *dst = location_add_pointer + location_len + 1;
        int need_add_before_lb = 0;
        if (location_add_pointer > buffer && *(location_add_pointer - 1) != '\n')
        {
            need_add_before_lb = 1;
            dst += 1;
        }
        int move_len = buffer + *size - src;
        int new_size = *size + (dst - src);
        if (new_size > max_size - 1)
            return -1;
        memmove(dst, src, move_len); // memmove >>
        *size = new_size;
        if (need_add_before_lb)
        {
            memcpy(location_add_pointer, "\n", 1);
            location_add_pointer += 1;
        }
        memcpy(location_add_pointer, location, location_len);
        location_add_pointer += location_len;
        memcpy(location_add_pointer, "\n", 1);
        location_add_pointer += 1;
        path_add_pointer = location_add_pointer;
    }

    // Add path
    char *src = path_add_pointer;
    char *dst = path_add_pointer + plugin_path_len + 1;
    int need_add_before_lb = 0;
    if (path_add_pointer > buffer && *(path_add_pointer - 1) != '\n')
    {
        need_add_before_lb = 1;
        dst += 1;
    }
    int move_len = buffer + *size - src;
    int new_size = *size + (dst - src);
    if (new_size > max_size - 1)
        return -1;
    memmove(dst, src, move_len); // memmove >>
    *size = new_size;
    if (need_add_before_lb)
    {
        memcpy(path_add_pointer, "\n", 1);
        path_add_pointer += 1;
    }
    memcpy(path_add_pointer, plugin_path, plugin_path_len);
    path_add_pointer += plugin_path_len;
    memcpy(path_add_pointer, "\n", 1);
    path_add_pointer += 1;

    buffer[*size] = '\0';
    return 0;
}

int modifyTaiConfigFile(const char *file, char *location, char *plugin_path, int priority)
{
    int res;

    int file_size = getFileSize(file);
    if (file_size < 0)
        return file_size;

    int location_len = strnlen(location, MAX_NAME_LENGTH);
    int plugin_path_len = strnlen(plugin_path, MAX_PATH_LENGTH);
    int alloc_size = file_size + location_len + plugin_path_len + 8;

    char *buffer = (char *)malloc(alloc_size);
    if (!buffer)
        return -1;

    int size = ReadFile(file, buffer, alloc_size - 1);
    if (size < 0)
    {
        free(buffer);
        return size;
    }
    buffer[size] = '\0';

    res = modifyTaiConfigBuffer(buffer, &size, alloc_size, location, plugin_path, priority);
    int ret = WriteFile(file, buffer, size);
    if (ret < 0)
        res = ret;
    free(buffer);

    return res;
}

int modifyTaiConfigFileEx(char *location, char *plugin_path, int priority)
{
    int res = -1;

    if (!checkFileExist("ur0:tai/config.txt"))
        resetTaihenConfig("ur0:tai/config.txt");

    int i;
    for (i = 0; i < N_TAIHEN_CONFIG_PATHS; i++)
    {
        if (modifyTaiConfigFile(taihen_config_paths[i], location, plugin_path, priority) >= 0)
            res = 0;
    }

    return res;
}

int installPluginFromBuffer(char *src_buf, int src_buf_size, char *location, char *dst_path, int priority)
{
    int res = modifyTaiConfigFileEx(location, dst_path, priority);
    int ret = WriteFile(dst_path, src_buf, src_buf_size);
    if (ret < 0)
        res = ret;
    return res;
}

int installPluginFromFile(char *src_path, char *location, char *dst_path, int priority)
{
    int res = modifyTaiConfigFileEx(location, dst_path, priority);
    int ret = copyFile(src_path, dst_path);
    if (ret < 0)
        res = ret;
    return res;
}
